`include "../../include/cpu_defines.sv"

module MultIssueQueue
#(parameter BUFFER_SIZE = 4)(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic clear,
	input logic stall,
	input logic en,

	input logic [5: 0] inst_rs,
	input logic [5: 0] inst_rt,
	input logic [5: 0] inst_rs2,
	input logic [5: 0] inst_rt2,
	input logic [5: 0] inst_rd1,
	input logic [5: 0] inst_rd2,
	input logic [`EXC_CODE_BUS] inst_exccode,
	input logic inst_rs_ready,
	input logic inst_rt_ready,
	input logic inst_rs2_ready,
	input logic inst_rt2_ready,
	input logic [31: 0] prf_src1,
	input logic [31: 0] prf_src2,
	input logic [`MULTOP_BUS] inst_multop,
	input logic inst_rob_direction,
	input logic [5: 0] rob_index1,
	input logic [5: 0] rob_index2,
	input logic wb_en1,
	input logic wb_en2,
	input logic wb_en3,
	input logic [5: 0] wb_rd1,
	input logic [5: 0] wb_rd2,
	input logic [5: 0] wb_rd3,
	input logic inst_acceptable,

	output logic [5: 0] search_rs,
	output logic [5: 0] search_rt,
	output logic out_inst_valid,
	output logic [5: 0] out_inst_rd1,
	output logic [5: 0] out_inst_rd2,
	output logic out_inst_src1_issign,
	output logic out_inst_src2_issign,
	output logic [31: 0] out_inst_src1,
	output logic [31: 0] out_inst_src2,
	output logic [31: 0] out_inst_src3,
	output logic [31: 0] out_inst_src4,
	output logic [31: 0] out_inst_src1_unsign,
	output logic [31: 0] out_inst_src2_unsign,
	output logic [5: 0] out_inst_rob_index1,
	output logic [5: 0] out_inst_rob_index2,
	output logic [`EXC_CODE_BUS] out_inst_exccode,
	output logic [`MULTOP_BUS] out_inst_type,
	output logic dis_mult_stall_request
);
	//`ASSERT((inst_aluop == `ALUOP_MULT || inst_aluop == `ALUOP_MULTU || inst_aluop == `ALUOP_DIV || inst_aluop == `ALUOP_DIVU));
	// type == 0: mult type == 1: div
	typedef struct packed{
		logic [`MULTOP_BUS] inst_type;
		logic [5: 0] rs, rt, rs2, rt2, rd1, rd2;
		logic [5: 0] rob_index1, rob_index2;
		logic [`EXC_CODE_BUS] exccode;
	} issue_element_t;

	issue_element_t buffer[BUFFER_SIZE-1: 0];
	logic [BUFFER_SIZE-1: 0] buffer_en;
	logic [BUFFER_SIZE-1: 0] rs_ready, rt_ready, rs2_ready, rt2_ready;
	logic [BUFFER_SIZE-1: 0] rob_direction;
	issue_element_t input_element;
	logic prf_valid, stage2;
	logic [`MULTOP_BUS] prf_inst_type;
	logic [5: 0] prf_rd1, prf_rd2, prf_rs, prf_rt, prf_rs2, prf_rt2;
	logic [5: 0] prf_rob_index1, prf_rob_index2;
	logic [31: 0] prf_save_src1, prf_save_src2;
	logic [`EXC_CODE_BUS] prf_exccode;
	logic store_enable;

	assign dis_mult_stall_request = (&buffer_en) & en;
	logic [$clog2(BUFFER_SIZE)-1: 0] free_index, select_index;
	logic [BUFFER_SIZE-1: 0] ready, select_index_decode;

	assign ready = buffer_en & rs_ready & rt_ready & rs2_ready & rt2_ready;
	assign store_enable = !stall && en;

	priority_encoder_4to2 encoder_free_index(~buffer_en, free_index);
	MultCompareUtil compare(
		.ready(ready),
		.index({buffer[3].rob_index1, buffer[2].rob_index1, buffer[1].rob_index1, buffer[0].rob_index1}),
		.direction(rob_direction),
		.select(select_index_decode)
	);
	encoder_4to2 encoder_select_index(select_index_decode, select_index);

	always_comb begin
		input_element.inst_type = inst_multop;
		input_element.rs = inst_rs;
		input_element.rt = inst_rt;
		input_element.rs2 = inst_rs2;
		input_element.rt2 = inst_rt2;
		input_element.rd1 = inst_rd1;
		input_element.rd2 = inst_rd2;
		input_element.rob_index1 = rob_index1;
		input_element.rob_index2 = rob_index2;
		input_element.exccode = inst_exccode;
	end

	always_ff @(posedge cpu_clk)begin
		if(cpu_rst_n == 1'b0 || clear)begin
			for(int i=0; i<BUFFER_SIZE; i++)begin
				buffer[i] <= 0;
				rs_ready[i] <= 0;
				rt_ready[i] <= 0;
				buffer_en[i] <= 0;
			end
			out_inst_valid 		<=	0; 
			out_inst_rd1 		<=	0; 
			out_inst_rd2 		<=	0; 
			out_inst_src1 		<=  0;
			out_inst_src2 		<= 	0;
			out_inst_src1_unsign 		<=	0; 
			out_inst_src2_unsign 		<=	0; 
			out_inst_rob_index1 <=	0; 
			out_inst_rob_index2 <=	0; 
			out_inst_type 		<=  0;
			out_inst_src1_issign <= 0;
			out_inst_src2_issign <= 0;
			out_inst_exccode <= `EXC_NONE;
			prf_valid <= 0;
			prf_rd1 <= 0;
			prf_rd2 <= 0;
			prf_rob_index1 <= 0;
			prf_rob_index2 <= 0;
			prf_rs <= 0;
			prf_rt <= 0;
			prf_inst_type <= 0;
			prf_exccode <= `EXC_NONE;
			rob_direction <= 0;
			stage2 <= 0;
			prf_save_src1 <= 0;
			prf_save_src2 <= 0;
			prf_rs2 <= 0;
			prf_rt2 <= 0;
			search_rs <= 0;
			search_rt <= 0;
		end
		else begin
			// wake up logic 
			for (int i = 0 ;i < BUFFER_SIZE ; i ++ ) begin
				if(buffer_en[i])begin
					rs_ready[i] <= rs_ready[i] || (wb_en1 && buffer[i].rs == wb_rd1) || (wb_en2 && buffer[i].rs == wb_rd2) || (wb_en3 && buffer[i].rs == wb_rd3);
					rt_ready[i] <= rt_ready[i] || (wb_en1 && buffer[i].rt == wb_rd1) || (wb_en2 && buffer[i].rt == wb_rd2) || (wb_en3 && buffer[i].rt == wb_rd3);
					rs2_ready[i] <= rs2_ready[i] || (wb_en1 && buffer[i].rs2 == wb_rd1) || (wb_en2 && buffer[i].rs2 == wb_rd2) || (wb_en3 && buffer[i].rs2 == wb_rd3);
					rt2_ready[i] <= rt2_ready[i] || (wb_en1 && buffer[i].rt2 == wb_rd1) || (wb_en2 && buffer[i].rt2 == wb_rd2) || (wb_en3 && buffer[i].rt2 == wb_rd3);
				end
			end
			if(inst_acceptable && stage2)begin
				search_rs <= prf_rs2;
				search_rt <= prf_rt2;
				prf_save_src1 <= prf_src1;
				prf_save_src2 <= prf_src2;
				prf_valid <= 1'b1;
				stage2 <= 1'b0;
			end
			if(inst_acceptable & ~stage2)begin			
				prf_valid 			<= 	(|ready) && !(buffer[select_index].inst_type == `MULTOP_MADD || buffer[select_index].inst_type == `MULTOP_MADDU || buffer[select_index].inst_type == `MULTOP_MSUB || buffer[select_index].inst_type == `MULTOP_MSUBU);
				stage2 				<= 	buffer[select_index].inst_type == `MULTOP_MADD || buffer[select_index].inst_type == `MULTOP_MADDU || buffer[select_index].inst_type == `MULTOP_MSUB || buffer[select_index].inst_type == `MULTOP_MSUBU;
				prf_rd1 			<=	buffer[select_index].rd1;
				prf_rd2 			<= 	buffer[select_index].rd2;
				prf_rs 				<= 	buffer[select_index].rs;
				prf_rt 				<= 	buffer[select_index].rt;
				prf_rs2 			<= 	buffer[select_index].rs2;
				prf_rt2 			<= 	buffer[select_index].rt2;
				search_rs			<= 	buffer[select_index].rs;
				search_rt 			<= 	buffer[select_index].rt;
				prf_rob_index1 		<= 	buffer[select_index].rob_index1;
				prf_rob_index2 		<= 	buffer[select_index].rob_index2;
				prf_inst_type 		<=  buffer[select_index].inst_type;
				prf_exccode 		<= 	buffer[select_index].exccode;
				if(|ready)begin
					buffer_en[select_index] <= 1'b0;
				end
			end
			
			if(inst_acceptable)begin
				out_inst_valid 		<= prf_valid;
				out_inst_rd1 		<= prf_rd1;
				out_inst_rd2 		<= prf_rd2;
				out_inst_rob_index1 <= prf_rob_index1;
				out_inst_rob_index2 <= prf_rob_index2;
				out_inst_type 		<= prf_inst_type;
				out_inst_src1  		<= prf_src1;
				out_inst_src2 		<= prf_src2;
				out_inst_src3 		<= prf_save_src1;
				out_inst_src4 		<= prf_save_src2;
				// not needed to consider rd because only it takes two stages from wakeup to get the operator
				if(prf_inst_type == `MULTOP_DIV || prf_inst_type == `MULTOP_MULT || prf_inst_type == `MULTOP_MUL || prf_inst_type == `MULTOP_MSUB) begin
					out_inst_src1_unsign <= (prf_src1[31]) ? ~(prf_src1) + 1 : prf_src1;
					out_inst_src2_unsign <= (prf_src2[31]) ? ~(prf_src2) + 1 : prf_src2;
					out_inst_src1_issign <= prf_src1[31];
					out_inst_src2_issign <= prf_src2[31];
				end
				else begin
					out_inst_src1_unsign <= prf_src1;
					out_inst_src2_unsign <= prf_src2;
					out_inst_src1_issign <= 0;
					out_inst_src2_issign <= 0;
				end
				out_inst_exccode <= prf_exccode;
			end

			if(en && ~stall)begin
				buffer_en[free_index] <= 1'b1;
				rs_ready[free_index] <= inst_rs_ready;
				rt_ready[free_index] <= inst_rt_ready;
				rs2_ready[free_index] <= inst_rs2_ready;
				rt2_ready[free_index] <= inst_rt2_ready;
				rob_direction[free_index] <= inst_rob_direction;
				buffer[free_index] <= input_element;
			end
		end
	end
endmodule

module MultCompareUtil #(
	parameter BANK_NUM=4
)(
	input logic [BANK_NUM-1: 0] ready,
	input logic [BANK_NUM-1: 0][5: 0] index,
	input logic [BANK_NUM-1: 0] direction,
	output logic [BANK_NUM-1: 0] select
);
	logic [5: 0] bigger;
	assign bigger[0] = ((direction[0] != direction[1]) ^ (index[0] < index[1]));
	assign bigger[1] = ((direction[0] != direction[2]) ^ (index[0] < index[2]));
	assign bigger[2] = ((direction[0] != direction[3]) ^ (index[0] < index[3]));
	assign bigger[3] = ((direction[1] != direction[2]) ^ (index[1] < index[2]));
	assign bigger[4] = ((direction[1] != direction[3]) ^ (index[1] < index[3]));
	assign bigger[5] = ((direction[2] != direction[3]) ^ (index[2] < index[3]));

	assign select[0] = ready[0] & ((~ready[1]) | ready[1] & bigger[0]) & ((~ready[2]) | ready[2] & bigger[1]) & ((~ready[3]) | ready[3] & bigger[2]);
	assign select[1] = ready[1] & ((~ready[0]) | ready[0] & ~bigger[0]) & ((~ready[2]) | ready[2] & bigger[3]) & ((~ready[3]) | ready[3] & bigger[4]);
	assign select[2] = ready[2] & ((~ready[0]) | ready[0] & ~bigger[1]) & ((~ready[1]) | ready[1] & ~bigger[3]) & ((~ready[3]) | ready[3] & bigger[5]);
	assign select[3] = ready[3] & ((~ready[0]) | ready[0] & ~bigger[2]) & ((~ready[1]) | ready[1] & ~bigger[4]) & ((~ready[2]) | ready[2] & ~bigger[5]);
endmodule